/*
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
 * 02111-1307, USA.
 *
 * http://www.gnu.org/copyleft/gpl.html
 */
package l1j.server.server.templates;

import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.List;
import java.util.logging.Logger;

import l1j.server.Base64;
import l1j.server.server.IdFactory;
import l1j.server.server.utils.L1QueryUtil;
import l1j.server.server.utils.L1QueryUtil.EntityFactory;

/**
 * ログインの為の様々なインターフェースを提供する.
 */
public class L1Account {
	private int _id;

	/** 계정명 */
	private String _name;
	/** 접속자 IP주소 */
	private String _ip;
	/** 패스워드(암호화 됨) */
	private String _password;
	/** 최종 액티브일.  */
	private Timestamp _lastActivatedAt;
	/** 액세스 레벨(GM인가? ).  */
	private int _accessLevel;
	/** 접속처의 호스트명.  */
	private String _host;
	/** 액세스 금지의 유무(True로 금지).  */
	private boolean _isBanned;
	/** 캐릭터의 추가 슬롯수 */
	private int _characterSlot;
	/** 어카운트가 유효한가 아닌가(True로 유효).  */
	private boolean _isValid = false;

	/** 메세지 로그용.  */
	private static Logger _log = Logger.getLogger(L1Account.class.getName());

	/**
	 * constructor　 　.
	 */
	private L1Account() {
	}

	/**
	 * 패스워드를 암호화한다.
	 *
	 * @param rawPassword 패스워드
	 * @return String
	 * @throws NoSuchAlgorithmException
	 *             암호화 알고리즘을 사용할 수 없을 때
	 * @throws UnsupportedEncodingException
	 *             인코딩이 지원되지 않을 때
	 */
	private static String encodePassword(final String rawPassword) {
		try {
			byte[] buf = rawPassword.getBytes("UTF-8");
			buf = MessageDigest.getInstance("SHA").digest(buf);

			return Base64.encodeBytes(buf);
		} catch (NoSuchAlgorithmException e) {
			throw new RuntimeException(e);
		} catch (UnsupportedEncodingException e) {
			throw new RuntimeException(e);
		}
	}

	static L1Account create(int id, final String name,
			final String rawPassword, final String ip, final String host) {
		String password = encodePassword(rawPassword);
		Timestamp currentTime = new Timestamp(System.currentTimeMillis());
		String sql = "INSERT accounts SET id = ?, name = ?, password = ?, last_activated_at = ?, access_level = ?, ip = ?, host = ?, is_banned = ?, character_slot = ?";
		L1QueryUtil.execute(sql, id, name, password, currentTime, 0, ip, host,
				false, 0);
		_log.info("created new account for " + name);
		return findById(id);
	}

	/**
	 * 신규 계정 생성
	 *
	 * @param name 계정명
	 * @param rawPassword 패스워드
	 * @param ip 접속자 IP주소
	 * @param host 접속자 호스트명
	 * @return Account
	 */

	public static L1Account create(final String name, final String rawPassword,
			final String ip, final String host) {
		return create(IdFactory.getInstance().nextId(), name, rawPassword, ip,
				host);
	}

	public static List<L1Account> findAll() {
		return L1QueryUtil.selectAll(new Factory(), "SELECT * FROM accounts");
	}

	public static List<L1Account> findByCharacterLevel(int minLevel,
			int maxLevel) {
		return L1QueryUtil
		.selectAll(
				new Factory(),
				"SELECT * FROM accounts WHERE name IN (SELECT DISTINCT(account_id) FROM characters WHERE level BETWEEN ? AND ?)",
				minLevel, maxLevel);
	}

	public static L1Account findById(int id) {
		return L1QueryUtil.selectFirst(new Factory(),
				"SELECT * FROM accounts WHERE id = ?", id);
	}

	/**
	 * 어카운트명으로부터 어카운트 정보를 검색한다
	 * 
	 * @param name
	 *            어카운트명
	 * @return Account
	 */
	public static L1Account findByName(final String name) {
		return L1QueryUtil.selectFirst(new Factory(),
				"SELECT * FROM accounts WHERE name = ?", name);
	}

	/**
	 * 최종 로그인일을 DB에 반영한다.
	 * 
	 */
	public void updateLastActivatedTime() {
		Timestamp currentTime = new Timestamp(System.currentTimeMillis());
		L1QueryUtil.execute(
				"UPDATE accounts SET last_activated_at = ? WHERE id = ?",
				currentTime, _id);
	}

	/**
	 * 슬롯수를 DB에 반영한다.
	 * 
	 */
	public void updateCharacterSlot() {
		L1QueryUtil.execute(
				"UPDATE accounts SET character_slot = ? WHERE id = ?",
				_characterSlot, _id);
		_log.fine("update characterslot for " + _name);
	}

	/**
	 * 캐릭터 소유수를 카운트 한다.
	 * 
	 * @return int
	 */
	public int countCharacters() {
		return L1QueryUtil.selectFirst(new CountFactory(),
				"SELECT count(*) as cnt FROM characters WHERE account_id = ?",
				_id);
	}

	/**
	 * 어카운트를 무효로 한다.
	 * 
	 * @param name
	 *            어카운트명
	 */
	public static void ban(final String name) {
		L1QueryUtil.execute(
				"UPDATE accounts SET is_banned = 1, WHERE name = ?", name);
	}

	/**
	 * 입력된 패스워드와 DB상의 패스워드를 조합한다.
	 * 
	 * @param rawPassword
	 *            평문패스워드
	 * @return boolean
	 * @throws IllegalStateException
	 *             이 오브젝트에 대해 메소드를 2회 이상 호출했을 경우
	 */
	public boolean validatePassword(final String rawPassword) {
		if (_isValid) { // 인증 성공 후에 재차 인증되었을 경우는 예외.
			throw new IllegalStateException("Account was validated twice.");
		}
		_isValid = _password.equals(encodePassword(rawPassword));
		if (_isValid) {
			_password = null; // 인증이 성공했을 경우, 패스워드를 파기한다.
		}
		return _isValid;
	}

	public int getId() {
		return _id;
	}

	/**
	 * 어카운트가 유효한가 어떤가를 돌려준다(True로 유효).
	 * 
	 * @return boolean
	 */
	public boolean isValid() {
		return _isValid;
	}

	/**
	 * 어카운트가 게임 마스터인가 어떤가 돌려준다(True로 게임 마스터).
	 * 
	 * @return boolean
	 */
	public boolean isGameMaster() {
		return 0 < _accessLevel;
	}

	/**
	 * 어카운트명을 취득한다.
	 * 
	 * @return String
	 */
	public String getName() {
		return _name;
	}

	/**
	 * 접속처의 IP주소를 취득한다.
	 * 
	 * @return String
	 */
	public String getIp() {
		return _ip;
	}

	/**
	 * 최종 로그인일을 취득한다.
	 */
	public Timestamp getLastActivatedAt() {
		return _lastActivatedAt;
	}

	/**
	 * 액세스 레벨을 취득한다.
	 * 
	 * @return int
	 */
	public int getAccessLevel() {
		return _accessLevel;
	}

	/**
	 * 호스트명을 취득한다.
	 * 
	 * @return String
	 */
	public String getHost() {
		return _host;
	}

	/**
	 * 액세스 금지 정보를 취득한다.
	 * 
	 * @return boolean
	 */
	public boolean isBanned() {
		return _isBanned;
	}

	/**
	 * 캐릭터의 추가 슬롯수를 취득한다.
	 * 
	 * @return int
	 */
	public int getCharacterSlot() {
		return _characterSlot;
	}

	public void setCharacterSlot(int i) {
		_characterSlot = i;
	}

	private static class CountFactory implements EntityFactory<Integer> {
		@Override
		public Integer fromResultSet(ResultSet rs) throws SQLException {
			return rs.getInt("cnt");
		}
	}

	private static class Factory implements EntityFactory<L1Account> {
		@Override
		public L1Account fromResultSet(ResultSet rs) throws SQLException {
			L1Account result = new L1Account();
			result._id = rs.getInt("id");
			result._name = rs.getString("name");
			result._password = rs.getString("password");
			result._lastActivatedAt = rs.getTimestamp("last_activated_at");
			result._accessLevel = rs.getInt("access_level");
			result._ip = rs.getString("ip");
			result._host = rs.getString("host");
			result._isBanned = rs.getBoolean("is_banned");
			result._characterSlot = rs.getInt("character_slot");
			return result;
		}
	}




}
